package com.zxp.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.zxp.common.core.security.authentication.MobileAuthenticationSecurityConfig;
import com.zxp.common.core.security.authentication.handler.MyAuthenticationSuccessHandler;
import com.zxp.common.core.security.properties.SecurityProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import java.util.ArrayList;

/**
 * 开启认证服务器功能
 *
 * @author shollin
 * @desc
 * @date 2021/7/9/009 17:04
 */
@Configuration
@EnableAuthorizationServer
@Slf4j
@RequiredArgsConstructor
public class MyAuthorizationServerServerConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;

    private final UserDetailsService userDetailsService;

    private final TokenStore tokenStore;
    private final TokenEnhancer tokenEnhancer;
    private final JwtAccessTokenConverter jwtAccessTokenConverter;

    private final ObjectMapper objectMapper;
    private final ClientDetailsService clientDetailsService;
    private final SecurityProperties securityProperties;
    private final AuthenticationFailureHandler authenticationFailureHandler;

    @Bean
    public MyAuthenticationSuccessHandler authenticationSuccessHandler(AuthorizationServerTokenServices authorizationServerTokenServices) {
        return new MyAuthenticationSuccessHandler(objectMapper, securityProperties, clientDetailsService, authorizationServerTokenServices);
    }

    @Bean
    public MobileAuthenticationSecurityConfig mobileAuthenticationSecurityConfig(AuthorizationServerTokenServices authorizationServerTokenServices) {
        return new MobileAuthenticationSecurityConfig(authenticationSuccessHandler(authorizationServerTokenServices), authenticationFailureHandler, userDetailsService);
    }

    //@Bean
    public DefaultTokenServices tokenService() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        //配置token存储
        tokenServices.setTokenStore(tokenStore);
        //开启支持refresh_token，此处如果之前没有配置，启动服务后再配置重启服务，可能会导致不返回token的问题，解决方式：清除redis对应token存储
        tokenServices.setSupportRefreshToken(true);
        //复用refresh_token
        tokenServices.setReuseRefreshToken(true);
        //token有效期，设置12小时
        tokenServices.setAccessTokenValiditySeconds(12 * 60 * 60);
        //refresh_token有效期，设置一周
        tokenServices.setRefreshTokenValiditySeconds(7 * 24 * 60 * 60);
        return tokenServices;
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //添加客户端信息
        clients.inMemory()
                .withClient("zxp")
                .secret("zxp123")
                .scopes("all", "read", "write")
                .redirectUris("http://www.baidu.com")
                .authorizedGrantTypes("authorization_code", "password", "refresh_token") //  "implicit"
                .accessTokenValiditySeconds(7200) // 过期时间，单位秒
                .refreshTokenValiditySeconds(30 * 24 * 3600);  //自动刷新过期时间，一个月
    }

   @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()") //  /oauth/token_key 访问tokenKey的时候，即SigningKey时放行 "isAuthenticated"验证，权限表达式
                .checkTokenAccess("permitAll()") // /oauth/check_token接口
                .allowFormAuthenticationForClients();
    }

    /**
     * Spring security5中新增加了加密方式，并把原有的spring security的密码存储格式改了
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        log.info("注入的tokenStore:" + tokenStore);
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);

        // 如果配置了jwt方式生成token，则加上这个
        if (jwtAccessTokenConverter != null && tokenEnhancer != null) {
            TokenEnhancerChain chain = new TokenEnhancerChain();
            ArrayList<TokenEnhancer> list = Lists.newArrayList();
            list.add(tokenEnhancer);
            list.add(jwtAccessTokenConverter);
            chain.setTokenEnhancers(list);
            endpoints
                    .tokenEnhancer(chain)
                    .accessTokenConverter(jwtAccessTokenConverter);
        }
    }
}
